﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace QQ2564874169.RelationalSql
{
    public interface IDbEntityQuery : IDbQueryCore
    {
        T Get<T>(T whereModel, bool isForUpdate = false, int? timeout = null);
        IEnumerable<T> Gets<T>(T whereModel, bool isForUpdate = false, DataSort sort = null, int? timeout = null);
        int Count<T>(T whereModel, int? timeout = null);
        bool Exists<T>(T whereModel, int? timeout = null);
        PageResult<T> Pager<T>(T whereModel, PageSort pageset, int? timeout = null);
    }

    public class DbEntityQuery: IDbEntityQuery
    {
        public static event Action<IDbEntityQuery> Created;
        public Hashtable Data { get; private set; }
        public bool InTransaction => _query.InTransaction;
        public event EventHandler<DbQueryBeforeEventArgs> Before
        {
            add { _query.Before += value; }
            remove { _query.Before -= value; }
        }
        public event EventHandler<DbQueryAfterEventArgs> After
        {
            add { _query.After += value; }
            remove { _query.After -= value; }
        }
        public event EventHandler<DbQueryErrorEventArgs> Error
        {
            add { _query.Error += value; }
            remove { _query.Error -= value; }
        }

        private IDbSqlQuery _query;

        public DbEntityQuery(IDbSqlQuery query)
        {
            Data = new Hashtable();

            _query = query;

            Created?.Invoke(this);
        }

        public virtual void Dispose()
        {
            Data?.Clear();
            Data = null;
            _query?.Dispose();
            _query = null;
        }

        public ITransactionScope BeginTransaction()
        {
            return _query.BeginTransaction();
        }

        public T Get<T>(T whereModel, bool isForUpdate = false, int? timeout = null)
        {
            if (isForUpdate && InTransaction == false)
                throw new InvalidOperationException("X锁查询必须在事务中使用。");
            var param = OnGet(whereModel, typeof(T), isForUpdate);
            return _query.Query<T>(param.Command, param.Parameters, param.IsProc, timeout).FirstOrDefault();
        }

        public IEnumerable<T> Gets<T>(T whereModel, bool isForUpdate = false, DataSort sort = null, int? timeout = null)
        {
            if (isForUpdate && InTransaction == false)
                throw new InvalidOperationException("X锁查询必须在事务中使用。");
            var param = OnGets(whereModel, typeof(T), isForUpdate, sort);
            return _query.Query<T>(param.Command, param.Parameters, param.IsProc, timeout);
        }

        public int Count<T>(T whereModel, int? timeout = null)
        {
            var param = OnCount(whereModel, typeof(T));
            return _query.QueryScalar<int>(param.Command, param.Parameters, param.IsProc, timeout);
        }

        public bool Exists<T>(T whereModel, int? timeout = null)
        {
            var param = OnExists(whereModel, typeof(T));
            return _query.QueryScalar<int>(param.Command, param.Parameters, param.IsProc, timeout) > 0;
        }

        public PageResult<T> Pager<T>(T whereModel, PageSort pageset, int? timeout = null)
        {
            if (pageset == null)
                throw new ArgumentNullException(nameof(pageset));

            var param = OnPager(whereModel, typeof(T), pageset);

            return _query.Pager<T>(param.Command, pageset, param.Parameters, param.IsProc, timeout);
        }

        protected virtual ExecuteParam OnPager(object model, Type tableType, PageSort pageset)
        {
            return OnGets(model, tableType, false, null);
        }

        protected virtual ExecuteParam OnGets(object model, Type tableType, bool isForUpdate, DataSort sort)
        {
            var map = ParseModel(model, tableType);
            var param = new ExecuteParam
            {
                Command = $"SELECT * FROM [{tableType.Name}]"
            };
            if (isForUpdate)
            {
                param.Command += " WITH (UPDLOCK)";
            }
            if (map.Count > 0)
            {
                param.Command += " WHERE " + string.Join(" AND ", map.Keys.Select(k => $"[{k}]=@{k}"));
                param.Parameters.Add(map);
            }
            if (sort != null && sort.Sorts.Count > 0)
            {
                param.Command += " " + sort.ToMssql();
            }
            return param;
        }

        protected virtual ExecuteParam OnGet(object model, Type tableType, bool isForUpdate)
        {
            var param = OnGets(model, tableType, isForUpdate, null);
            param.Command = param.Command.Replace("SELECT *", "SELECT TOP 1 *");
            return param;
        }

        protected virtual ExecuteParam OnCount(object model, Type tableType)
        {
            var param = OnGets(model, tableType, false, null);
            param.Command = param.Command.Replace("SELECT *", "SELECT COUNT(1)");
            return param;
        }

        protected virtual ExecuteParam OnExists(object model, Type tableType)
        {
            var param = OnGet(model, tableType, false);
            param.Command = param.Command.Replace("TOP 1 *", "TOP 1 1");
            return param;
        }

        protected static Dictionary<string, object> ParseModel(object model, Type modelType = null)
        {
            modelType = modelType ?? model.GetType();
            var ps = modelType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
            var dict = new Dictionary<string, object>();
            foreach (var p in ps)
            {
                var v = p.GetValue(model);
                if (v != null)
                {
                    dict.Add(p.Name, v);
                }
            }
            return dict;
        }
    }
}
